If you don't understand prototypes, you don't understand Javascript.
如果你沒搞懂原型,你就不算懂 Javascript.
Javascript 與其他語言最大的不同是它沒有像 Java, C++ 一樣有 class (類)的概念,如果有了class的概念,勢必會增加像是 抽象(abstract), 多型(polymorphism), 封裝 (encapsulation) 等原生基礎概念,會提高學習難度(雖然可以後續實作),但 Javascript 還是需要有繼承的方法,那它是透過原型 (Prototype) 機制實現相互繼承功能。
舉例來說:
function Cat(name) {
this.name = name;
this.species = '貓科';
this.log = function() {
console.log('name' + this.name + 'species' + this.species);
}
}
var ScottishFold = new Cat('Scottish Fold');
var Lion = new Cat('Lion');
console.log(ScottishFold.name); // ScottishFold
console.log(Lion.name); // Lion
console.log(ScottishFold.log === Lion.log); // false
以這裡來說 Cat
就是一個構造函數 (constructor),表示對象 ScottishFold 和 Lion 的原型(Prototype),而利用構造函數就可以創造出一個 Cat 的實例(instance)。
但也因為是用 new ,所以 ScottishFold 和 Lion 都是獨立的,各佔據記憶體,所以 ScottishFold.log != Lion.log,這也是 new 的缺點,也因此有了原型鏈(prototype chain)的產生。
在構造函數創建的時候,系統默認的幫構造函數創建關聯一個對象,這個對象就是原型
在原型中的所有屬性跟方法,都可以被和其關聯的構造函數創建出來的所有對象共享
function Cat(name) {
this.name = name;
}
Cat.prototype.playWithMe = function() {
console.log(this.name+' play with me now');
}
var ScottishFold = new Cat('Scottish Fold');
ScottishFold.playWithMe(); // Scottish Fold play with me now
ScottishFold.name = 'White Scottish Fold';
ScottishFold.playWithMe(); // White Scottish Fold play with me now
console.log(ScottishFold.__proto__ == Cat.prototype); //true
console.log(ScottishFold.__proto__.__proto__ == Object.prototype); //true
console.log(Cat.prototype);
console.log(ScottishFold);
在這裡要解釋一下,__proto__
與prototype
的不同:
__proto__
是每個對象都會有的一個屬性,prototype
是函數(function)才會有的屬性__proto__
指向的是當前對象
的原型對象,prototype
指向的是當前原始構造函數
的原型對象所以這裡要注意,Cat.prototype 不是 Cat 原型,而是構造函數執行後建立的新物件原型。
(請參照console.log(Cat.prototype)裡的 __proto__
是 Object唷)
以下為流程圖:
問題:
每個對象都有原型,原型本身又是一個原型,通過
__proto__
向上指,最後會先變成 Object,在往上就變成 null,一個對象通過原型鏈會擁有定義在其他對象中的屬性和方法
function Cat(name) {
this.name = name;
}
var ScottishFold = new Cat('Scottish Fold');
console.log(ScottishFold); // Cat{name: 'Scottish Fold'}
console.log(ScottishFold.__proto__ === Cat.prototype); // true
console.log(ScottishFold.__proto__.__proto__ === Object.prototype); //true
console.log(ScottishFold.__proto__.__proto__.__proto__ === null); //true
ScottishFold 雖然沒有 constructor 屬性,但會通過原型鏈向上找 __proto__
,最終找到指向 Cat.prototype。
Object.defineProperty(Object.prototype,'__proto__',{
get: function(){
return Object.getPrototypeOf(this) // 獲取對象的[[Prototype]]
},
set: function(o){
Object.setPrototypeOf(this,o) // 設置對象的[[Prototype]]關聯的原型為o
return o
}
})
其中 [[Prototype]]
是一個設定 Object 的 Prototype 的接口,是一個隱藏屬性,屬性指向對象的原型,其實也可以理解為瀏覽器顯示的__proto__
屬性,在 ES5 透過 getPrototypeOf 取得,透過 setPrototypeOf 修改。
QQ不小心按到重整又忘記存了
Chapter 17. Objects and Inheritance